/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "xpm_common.h"

#include <stdio.h>
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "selinux/selinux.h"
#include "file_ex.h"

namespace OHOS {
namespace Security {
namespace XpmUnitTest {
int GetXpmRegion(struct XpmRegionArea &area)
{
    if (InitXpmRegion() != 0) {
        XPM_LOG_ERROR("init xpm region failed");
        return -1;
    }

    pid_t pid = getpid();
    std::string path = XPM_PROC_PREFIX_PATH + std::to_string(pid) + XPM_PROC_SUFFIX_PATH;
    int fd = open(path.c_str(), O_RDWR);
    if (fd < 0) {
        XPM_LOG_ERROR("open xpm proc file failed(%{public}s)", strerror(errno));
        return -1;
    }

    char xpm_region[XPM_PROC_LENGTH] = {0};
    int ret = read(fd, xpm_region, sizeof(xpm_region));
    if (ret < 0) {
        XPM_LOG_ERROR("read xpm proc file failed(%{public}s)", strerror(errno));
        return -1;
    }

    ret = sscanf(xpm_region, "%llx-%llx", &area.start, &area.end);
    if (ret < 0) {
        XPM_LOG_ERROR("sscanf xpm region string failed(%{public}s)", strerror(errno));
        return -1;
    }

    close(fd);
    return 0;
}

bool IsXpmRegionInner(unsigned long start, unsigned long end)
{
    struct XpmRegionArea area = {0};

    int ret = GetXpmRegion(area);
    if (ret < 0) {
        XPM_LOG_ERROR("get xpm region failed");
        return false;
    }

    if (start >= area.start && end <= area.end) {
        return true;
    }

    return false;
}

bool IsXpmRegionOuter(unsigned long start, unsigned long end)
{
    struct XpmRegionArea area = {0};

    int ret = GetXpmRegion(area);
    if (ret < 0) {
        XPM_LOG_ERROR("get xpm region failed");
        return false;
    }

    if (start >= area.end || end <= area.start) {
        return true;
    }

    return false;
}

int SetCon(const std::string &type)
{
    return setcon(type.c_str());
}

int ExecSumFun(void *addr)
{
    int ret = ((SumFunc)addr)(1, 2);

    printf("ret is %d\n", ret);
    return ret == 4 ? 0 : -1;
}

int AccessData(void *addr, int op)
{
    uint8_t val;
    switch (op) {
    case OP_READ: //只读
        val = ((uint8_t *)addr)[0];
        break;
    case OP_WRITE:{ //写前读
        val = ((uint8_t *)addr)[0];
        ((uint8_t *)addr)[100] = val;
    }
        break;
    case OP_WRITE_ONLY: //只写
        ((uint8_t *)addr)[100] = 3;
        break;
    default:
        break;
    }

    return 0;
}

static int GetElf64Segment(uint8_t *buf, unsigned long &offset, unsigned long &size, uint32_t flag)
{
    Elf64_Ehdr *ehdr = (Elf64_Ehdr *)buf;
    uint32_t phNum = ehdr->e_phnum;
    Elf64_Phdr *phdr = (Elf64_Phdr *)&buf[ehdr->e_phoff];

    for (int i = phNum - 1; i >= 0; i--) {
        if ((phdr[i].p_type == PT_LOAD) && ((phdr[i].p_flags & flag) == flag)) {
            offset = phdr[i].p_offset;
            size = phdr[i].p_filesz;
            return 0;
        }
    }

    return -1;
}

static int GetElf32Segment(uint8_t *buf, unsigned long &offset, unsigned long &size, uint32_t flag)
{
    Elf32_Ehdr *ehdr = (Elf32_Ehdr *)buf;
    uint32_t phNum = ehdr->e_phnum;
    Elf32_Phdr *phdr = (Elf32_Phdr *)&buf[ehdr->e_phoff];

    for (int i = phNum - 1; i >= 0; i--) {
        if ((phdr[i].p_type == PT_LOAD) && ((phdr[i].p_flags & flag) == flag)) {
            offset = phdr[i].p_offset;
            size = phdr[i].p_filesz;
            return 0;
        }
    }

    return -1;
}

static int GetFileStat(const std::string &path, int &fd, uint32_t &length)
{
    fd = open(path.c_str(), O_RDONLY);
    if (fd < 0) {
        XPM_LOG_ERROR("open file %{public}s failed: %{public}s", path.c_str(), strerror(errno));
        return -1;
    }

    struct stat fileState = {0};
    if (fstat(fd, &fileState) != 0) {
        XPM_LOG_ERROR("fsstat %{public}s failed", path.c_str());
        close(fd);
        return -1;
    }

    if (fileState.st_size < EI_NIDENT) {
        XPM_LOG_ERROR("file %{public}s stat size invalid", path.c_str());
        close(fd);
        return -1;
    }
    length = fileState.st_size;

    return 0;
}

static int ParseElfInfo(uint8_t *addr, unsigned long &offset, unsigned long &size, uint32_t flag)
{
    int ret = -1;

    switch (addr[EI_CLASS]) {
        case ELFCLASS64:
            ret = GetElf64Segment(addr, offset, size, flag);
            break;
        case ELFCLASS32:
            ret = GetElf32Segment(addr, offset, size, flag);
            break;
        default:
            break;
    }

    return ret;
}

static int GetSegment(const std::string &path, unsigned long &offset, unsigned long &size, uint32_t flag)
{
    int fd;
    uint32_t length;

    if (path.length() == 0) {
        XPM_LOG_ERROR("input path is invalid");
        return -1;
    }

    int ret = GetFileStat(path, fd, length);
    if (ret != 0) {
        XPM_LOG_ERROR("get file %{public}s stat failed", path.c_str());
        return ret;
    }

    void *addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
    if (addr == MAP_FAILED) {
        XPM_LOG_ERROR("mmap failed: %{public}s", strerror(errno));
        close(fd);
        return -1;
    }

    ret = -1;
    do {
        if (memcmp(addr, ELFMAG, SELFMAG) != 0) {
            XPM_LOG_ERROR("file is not elf");
            break;
        }

        ret = ParseElfInfo(reinterpret_cast<uint8_t *>(addr), offset, size, flag);
        if (ret != 0) {
            XPM_LOG_ERROR("parset elf info failed");
            break;
        }
    } while (0);
    munmap(addr, length);
    close(fd);

    return ret;
}

int GetCodeSegment(const std::string &path, unsigned long &offset, unsigned long &size)
{

    return GetSegment(path, offset, size, PF_X);
}

int GetDataSegment(const std::string &path, unsigned long &offset, unsigned long &size)
{
    return GetSegment(path, offset, size, PF_R | PF_W);
}
} // namespace XpmUnitTest
} // namespace Security
} // namespace OHOS